"""
quick reference:
https://docs.singtown.com/micropython/zh/latest/esp32/esp32/quickref.html
http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
https://help.aliyun.com/document_detail/73742.html?spm=a2c4g.11186623.6.658.569b821aW08KOl

the following info is private！
ProductKey: a15NNbDLsRb
DeviceName: ESP32_MicroPython
DeviceSecret: b48e507d6148b9c821f0716c37c2d328
"""
import dht
import time
import json
import utime
import network
import onewire
import ds18x20
import bmp280
import urequests
from machine import Pin, I2C, Timer, RTC, ADC, PWM
from simple import MQTTClient
from ssd1306 import SSD1306_I2C

# 温度传感器使用
ow = onewire.OneWire(Pin(4))
temperature_sensor = ds18x20.DS18X20(ow)
ow_addr = temperature_sensor.scan()

# 温湿度传感器使用
humidity_sensor = dht.DHT11(Pin(27))

# 光敏传感器
light_sensor = ADC(Pin(32))
light_sensor.atten(ADC.ATTN_11DB)

# 大气压强传感器
BMP = bmp280.BMP280(I2C(sda=Pin(16), scl=Pin(17)))

# 一号舵机
Servo1 = PWM(Pin(18), freq=50, duty=0)

# 定时器
tim_oled = Timer(-1)
tim_sensor = Timer(-2)
tim_rtc = Timer(-3)

# OLED显示屏
oled = SSD1306_I2C(128, 64, I2C(sda=Pin(13), scl=Pin(14)), addr=0x3C)

# 无线局域网
wlan = network.WLAN(network.STA_IF)

# MQTT通信客户端
mqtt_client = mqtt_client = MQTTClient(
    client_id='ESP_32SYX|securemode=3,signmethod=hmacsha1,timestamp=233|',
    server='a15NNbDLsRb.iot-as-mqtt.cn-shanghai.aliyuncs.com',
    port=1883,
    user='ESP32_MicroPython&a15NNbDLsRb',
    password='d35c48076f485369f97af3127dd71a76f5579c66',
    keepalive=60,
)

# 所有已知wifi
known_wifi = [
    ('cugbwifi', ''),
    ('复变函数与积分变换', 'Syx19981018'),
    ('MIIX520-SYX', '12345678'),
]

# 实时时钟
rtc = RTC()
work_start = -1
rtc_calibrate = 0

# 传感器数据
humi = 0
temp1 = 0  # DS18B20提供
temp2 = 0  # DHT11提供
temp3 = 0  # BMP280提供
light = 0
pressure = 0
altitude = 0

# 用户命令
user_settings = {
    'Display': 'Date',
    'Servo': 0,
}

def deinit_all():
    mqtt_client.disconnect()
    wlan.disconnect()
    tim_oled.deinit()
    tim_sensor.deinit()
    tim_rtc.deinit()
    rtc.deinit()


def get_temperature() -> float:
    temperature_sensor.convert_temp()
    return temperature_sensor.read_temp(ow_addr[0])


def get_temperature_and_humidity() -> (int, int):
    humidity_sensor.measure()
    return humidity_sensor.temperature(), humidity_sensor.humidity()


def get_brightness() -> float:
    val = light_sensor.read()
    return 1 - val / 4096


def get_pressure_altitude_and_temperature() -> (int, float, float):
    return BMP.getPress(), BMP.getAltitude(), BMP.getTemp()


def set_servo_angle(s, a):
    s.duty(int(((a + 90) * 2 / 180 + 0.5) / 20 * 1023))


def tim_oled_handler(t):
    global oled, wlan

    ip_now = wlan.ifconfig()[0]
    run_time = time.time() - work_start
    run_second = run_time % 60
    run_minute = (run_time // 60) % 60
    run_hour = (run_time // 3600) % 60
    year, month, day, weekday, hour, minute, second, millisecond \
        = rtc.datetime()
    week = [
        'Monday',
        'Tuesday',
        'Wednesday',
        'Thursday',
        'Friday',
        'Saturday',
        'Sunday',
    ]

    if ip_now == '0.0.0.0':
        deinit_all()
        return

    if user_settings['Display'] == 'Date':
        oled.fill(0)
        oled.text(wlan.config('essid') + ' MQTT', 0, 0)
        oled.text(ip_now, 0, 10)
        oled.text(
            'exec:%04d-%02d-%02d' % (run_hour, run_minute, run_second),
            0,
            20,
        )
        oled.text(
            '%04d-%02d-%02d' % (year, month, day),
            0,
            30,
        )
        oled.text(
            '  %02d:%02d:%02d' % (hour, minute, second),
            0,
            40,
        )
        oled.text(
            '  ' + week[weekday],
            0,
            50,
        )
        oled.show()
    elif user_settings['Display'] == 'Sensor':
        oled.fill(0)
        oled.text('humi : ' + str(humi) + '%', 0, 0)
        oled.text('temp : ' + str((temp1 + temp2 + temp3) / 3), 0, 10)
        oled.text('pres : ' + str(pressure) + 'Pa', 0, 20)
        oled.text('light: ' + ('%.2f' % light) + '%', 0, 30)
        oled.text('alti : ' + str(altitude) + 'm', 0, 40)
        oled.text(
            '  %02d:%02d:%02d' % (hour, minute, second),
            0,
            50,
        )
        oled.show()


def tim_sensor_handler(t):
    global mqtt_client, temp1, temp2, temp3, humi, light, pressure, altitude

    temp1 = get_temperature()
    temp2, humi = get_temperature_and_humidity()
    light = get_brightness()
    pressure, altitude, temp3 = get_pressure_altitude_and_temperature()
    data = {
        'params': {
            'temperature1': temp1,
            'temperature2': temp2,
            'temperature3': temp3,
            'humidity': humi,
            'brightness': light,
            'pressure': pressure,
            'altitude': float(altitude),
        },
        'method': 'thing.event.property.post',
    }

    try:
        mqtt_client.publish(
            '/sys/a15NNbDLsRb/ESP32_MicroPython/thing/event/property/post',
            str(data),
            qos=1,
            retain=False
        )
        mqtt_client.check_msg()
    except Exception as e:
        print(e)
        deinit_all()
        oled.fill(0)
        oled.text('MQTT publis', 0, 10)
        oled.text('failed, resetting', 0, 20)
        oled.show()
        time.sleep_ms(2000)
    else:
        print(rtc.datetime())
        print(str(data), end='\n\n')


def tim_rtc_handler(t):
    global rtc_calibrate
    rtc_calibrate += 10
    if rtc_calibrate >= 7 * 60 * 60:  # 7个小时
        rtc_calibrate = 0
        rtc.deinit()

        resp = json.loads(
            urequests.get(
                'http://api.m.taobao.com/' +
                'rest/api3.do?api=mtop.common.getTimestamp'
            ).text,
        )

        # 嵌入式设备使用2020-1-1（UTC）作为0时刻
        second_now = int(resp['data']['t']) // 1000 + \
            8 * 60 * 60 - 946684800
        millisecond = int(resp['data']['t']) % 1000

        year, month, day, hour, minute, \
            second, weekday, yearday = \
            utime.localtime(second_now)

        rtc.init((
            year,
            month,
            day,
            weekday,
            hour,
            minute,
            second,
            millisecond,
        ))

        print(year, month, day, hour, minute, second)
        print(weekday, yearday)


def mqtt_subscriber_handler(topic: str, msg: str):
    global user_settings

    topic = topic.decode('utf-8')
    msg = msg.decode('utf-8')

    print(topic)
    print(msg)

    if topic == '/a15NNbDLsRb/ESP32_MicroPython/user/operation':
        try:
            msg = int(msg)
        except Exception as e:
            print(e)
        else:
            if msg == 1:
                user_settings['Display'] = 'Date'
            elif msg == 2:
                user_settings['Display'] = 'Sensor'

    if topic == '/a15NNbDLsRb/ESP32_MicroPython/user/servo':
        try:
            msg = int(msg)
        except Exception as e:
            print(e)
        else:
            user_settings['Servo'] = msg


def connect_wifi(name: str, password: str) -> bool:
    global wlan
    led = Pin(2, Pin.OUT)

    led.value(0)
    wlan.active(True)
    start_time = time.time()

    if not wlan.isconnected():
        wlan.connect(name, password)
    elif wlan.config('essid') != name:  # wifi名称
        wlan.disconnect()
    else:
        oled.value(1)
        return True

    while not wlan.isconnected():
        led.value(1)
        time.sleep_ms(100)
        led.value(0)
        time.sleep_ms(100)

        if time.time() - start_time > 10:
            led.value(0)
            wlan.active(False)
            return False
    led.value(1)
    return True


while True:
    # 连接到一个已知的WIFI
    while not wlan.isconnected():
        for wifi in known_wifi:
            if not wlan.isconnected():
                mac = wlan.config("mac")
                oled.fill(0)
                oled.text("try %s" % wifi[0], 0, 0)
                oled.text("%02x:%02x:%02x" % (mac[0], mac[1], mac[2]), 0, 20)
                oled.text("%02x:%02x:%02x" % (mac[3], mac[4], mac[5]), 0, 40)
                oled.show()
                if connect_wifi(wifi[0], wifi[1]):
                    oled.fill(0)
                    oled.text("success", 0, 0)
                    oled.show()
                    time.sleep_ms(2000)

                    try:
                        ip, subnet, gateway, dns = wlan.ifconfig()
                        mqtt_client.set_callback(mqtt_subscriber_handler)
                        mqtt_client.connect()
                        mqtt_client.subscribe(
                            '/a15NNbDLsRb/ESP32_MicroPython/user/operation'
                        )
                        mqtt_client.subscribe(
                            '/a15NNbDLsRb/ESP32_MicroPython/user/servo'
                        )
                        resp = json.loads(
                            urequests.get(
                                'http://api.m.taobao.com/' +
                                'rest/api3.do?api=mtop.common.getTimestamp'
                            ).text,
                        )
                        # 嵌入式设备使用2020-1-1（UTC）作为0时刻
                        second_now = int(resp['data']['t']) // 1000 + \
                            8 * 60 * 60 - 946684800
                        millisecond = int(resp['data']['t']) % 1000
                        if work_start == -1:
                            work_start = second_now
                        year, month, day, hour, minute, \
                            second, weekday, yearday = \
                            utime.localtime(second_now)
                        rtc.init((
                            year,
                            month,
                            day,
                            weekday,
                            hour,
                            minute,
                            second,
                            millisecond,
                        ))
                        print(year, month, day, hour, minute, second)
                        print(weekday, yearday)
                        time.sleep_ms(3000)
                    except Exception as e:
                        print('\n\nError!')
                        print(e)
                        print('\n\n')
                        time.sleep_ms(5000)

                        deinit_all()
                        oled.fill(0)
                        oled.text('failed to ', 0, 0)
                        oled.text('connect MQTT', 0, 10)
                        oled.text('resetting..', 0, 20)
                        oled.show()
                        time.sleep_ms(2000)
                    else:
                        oled.fill(0)
                        oled.text(wlan.config('essid'), 0, 0)
                        oled.text('MQTT connected.', 0, 20)
                        oled.text(ip, 0, 40)
                        oled.show()
                        time.sleep_ms(2000)

                        tim_oled.init(
                            period=1000,
                            mode=Timer.PERIODIC,
                            callback=tim_oled_handler
                        )
                        tim_sensor.init(
                            period=5000,
                            mode=Timer.PERIODIC,
                            callback=tim_sensor_handler
                        )
                        tim_rtc.init(
                            period=10000,
                            mode=Timer.PERIODIC,
                            callback=tim_rtc_handler
                        )
                else:
                    oled.fill(0)
                    oled.text('failed to', 0, 0)
                    oled.text('connect to wifi', 0, 10)
                    oled.text(wifi[0], 0, 20)
                    oled.text('try next', 0, 30)
                    oled.show()
                    time.sleep_ms(2000)

    a = user_settings['Servo']
    set_servo_angle(Servo1, a)
    time.sleep_ms(300)
